package org.example;

import java.io.*;
import java.util.LinkedList;

public class QTreeSerializer {
    // 序列化标记
    private final byte LEAF_NODE_MARKER = 0;
    private final byte INTERNAL_NODE_MARKER = 1;

    /**
     * 自定义序列化方法（BFS顺序）
     * 格式：
     *   [节点类型标记][bounds][可选数据]
     *   叶子节点: 0 [x:Int][y:Int][size:Int][value:Boolean]
     *   内部节点: 1 [x:Int][y:Int][size:Int]
     */
    public void serializeCustom(QuadTree root, String filePath) {
        try {
            final var dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(filePath)));
            var queue = new LinkedList<QuadTree>();
            queue.add(root);

            while (!queue.isEmpty()) {
                var node = queue.remove();
                if (node.getClass() == Leaf.class) {
                    // 写入叶子节点标记
                    dos.writeByte(LEAF_NODE_MARKER);
                    Leaf l = (Leaf)node;
                    // 写入bounds
                    writeBounds(dos, l.getBound());
                    // 写入值
                    var bVal = !l.isObstacle;
                    dos.writeBoolean(bVal);
                }
                else if (node.getClass() == TreeNode.class) {
                    TreeNode n = (TreeNode)node;
                    dos.writeByte(INTERNAL_NODE_MARKER);
                    writeBounds(dos, n.bound);  // 写入bounds
                    // 将子节点加入队列（保证顺序）
                    queue.add(n.nw);
                    queue.add(n.ne);
                    queue.add(n.sw);
                    queue.add(n.se);
                }
            }
            dos.close();
        } catch (IOException e) {
            System.out.println("发生异常：" + e);
        }
    }
    // 辅助方法：写入Bounds
    private void writeBounds(DataOutputStream dos, Bounds bounds) throws IOException {
        dos.writeInt(bounds.x);
        dos.writeInt(bounds.y);
        dos.writeInt(bounds.size);
    }
    /**
     * 自定义反序列化方法
     */
    static class TreeNodeIdx {
        public TreeNode node;
        public int idx;
        TreeNodeIdx(TreeNode node, int idx) {
            this.node = node;
            this.idx = idx;
        }
    }

    public QuadTree deserializeCustom(String filePath) {
        var nodeQueue = new LinkedList<TreeNodeIdx>(); // (父节点, 子节点索引)
        QuadTree root = null;
        try {
            var dis = new DataInputStream(new BufferedInputStream(new FileInputStream(filePath)));
            while (dis.available() > 0) {
                final var marker = dis.readByte();
                final var bounds = readBounds(dis);
                QuadTree n = null;
                if (marker == LEAF_NODE_MARKER) {
                    var value = dis.readBoolean();
                    n = new Leaf(bounds, !value);
                } else if (marker == INTERNAL_NODE_MARKER) {
                    // 创建临时内部节点（4个子元素先置空）
                    var node = new TreeNode(null, null, null, null, bounds);
                    // 加入队列等待填充子节点
                    nodeQueue.add(new TreeNodeIdx(node, 0));
                    n = node;
                } else {
                    throw new IOException("Invalid marker byte: " + marker);
                }

                // 设置根节点或填充父节点的子节点
                if (root == null) {
                    root = n;
                } else {
                    fillParentChild(nodeQueue, n);
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return root;
    }
    // 辅助方法：读取Bounds
    private Bounds readBounds(DataInputStream dis) throws IOException {
        var x = dis.readInt();
        var y = dis.readInt();
        var size = dis.readInt();
        return new Bounds(x, y, size);
    }

    // 辅助方法：填充父节点的子节点
    private void fillParentChild(LinkedList<TreeNodeIdx> queue, QuadTree node) {
        if (!queue.isEmpty()) {
            TreeNodeIdx tni = queue.peek();
            var parent = tni.node;
            var idx = tni.idx;
            // 子节点的写入顺序是：n.nw n.ne n.sw n.se
            switch (idx) {
                case 0:
                    parent.nw = node;
                    break;
                case 1:
                    parent.ne = node;
                    break;
                case 2:
                    parent.sw = node;
                    break;
                case 3:
                    parent.se = node;
                    break;
                default:
                    throw new RuntimeException("fillParentChild: no such idx");
            }
            if (idx == 3) {
                queue.remove();
            } else {
                queue.set(0, new TreeNodeIdx(parent, idx + 1));
            }
        }
    }

    public static void main(String[] args) {
        System.out.println("We are now running in QTreeSerializer.main.");
        System.out.println("we are going to:");
        System.out.println("- Deserialize QTree from a binary file(read from serializeBinary.data, map size is 512x512).");
        System.out.println("- Visualize QTree in grids.png");
        System.out.println("- Visualize neighborhoods in grids.png");
        System.out.println("- Visualize path finding in grids.png");
        var t1 = System.currentTimeMillis();
        var seri = new QTreeSerializer();
        var quadTree = seri.deserializeCustom("/var/flinkrc/serializeBinary.data");
        System.out.println("[****]deserializeCustom finished in "+(System.currentTimeMillis()-t1)+" ms[****]");

        var t2 = System.currentTimeMillis();
        var afg = new QTreeFromGrid();
        afg.computeAllNeighbors(quadTree);
        System.out.println("[****]computeAllNeighbors finished in "+(System.currentTimeMillis()-t2)+" ms[****]");

        // 寻路测试
        var t3 = System.currentTimeMillis();
        var route = new AStar(quadTree).findPath(new Point(110, 338), new Point(484, 372));
        System.out.println("[****]route work finished in "+(System.currentTimeMillis()-t3)+" ms[****]");
        System.out.println(route);

        var rnd = new QTreeRender();
        rnd.renderQuadTree(quadTree, 512, "/var/flinkrc/grids2.png", route);

        seri.serializeCustom(quadTree, "/var/flinkrc/serializeBinary1234.data");
        seri.deserializeCustom("/var/flinkrc/serializeBinary1234.data");
    }
}
